from odoo import models, fields, api, _
from odoo.exceptions import UserError, ValidationError


class SBCreditNoteApplyLine(models.Model):
    _name = 'sb.credit.note.apply.line'
    _description = 'Invoice Adjustment Line'

    adjustment_id = fields.Many2one('sb.credit.note.apply', string='Adjustment Reference', required=True, ondelete='cascade')

    invoice_line_id = fields.Many2one(
        'account.move.line', string='Invoice Line', ondelete='restrict',
        domain="[('move_id', 'in', parent.invoices_to_adjust_ids), ('move_id.state', '=', 'draft')]"
    )

    original_price_unit = fields.Float(string='Original Unit Price', related='invoice_line_id.price_unit', readonly=True, digits='Product Price')
    original_currency = fields.Many2one(string='Original Currency', related='adjustment_id.currency_id', readonly=True, )
    original_price_subtotal = fields.Monetary(
        string='Original Subtotal', related='invoice_line_id.price_subtotal',
        currency_field='original_currency',
        readonly=True, digits='Product Price'
    )
    product_id = fields.Many2one('product.product', string='Product', related='invoice_line_id.product_id', readonly=True)
    original_quantity = fields.Float(string='Original Quantity', readonly=True, digits='Product Unit of Measure')
    quantity = fields.Float(string='Quantity', readonly=True, digits='Product Unit of Measure', default=1)
    new_price_unit = fields.Float(string='New Unit Price', digits='Product Price')

    new_price_subtotal = fields.Monetary(
        string='New Subtotal', digits='Product Price',
        currency_field='original_currency',
        compute='_compute_new_price_subtotal', store=True
    )
    label = fields.Char(string='Label', readonly=True)
    adjustment_type = fields.Selection([
        ('unit_price', 'Adjust Unit Price'),
        ('subtotal', 'Adjust Subtotal'),
    ], string='Adjustment Type', default='unit_price')
    tax_ids = fields.Many2many(
        'account.tax', string='Taxes',
        default=lambda self: self.invoice_line_id.tax_ids if self.invoice_line_id else False,
        help='Taxes applied to the invoice line.',
        domain="[('type_tax_use', '=', 'sale')]"
    )

    @api.depends('new_price_unit', 'quantity')
    def _compute_new_price_subtotal(self):
        for rec in self:
            if rec.adjustment_type == 'unit_price':
                rec.new_price_subtotal = rec.new_price_unit * rec.quantity
            elif rec.adjustment_type == 'subtotal':
                rec.new_price_subtotal = rec.new_price_subtotal

    @api.onchange('invoice_line_id')
    def _onchange_invoice_line_id(self):
        if self.invoice_line_id:
            self.new_price_unit = self.invoice_line_id.price_unit
            self.new_price_subtotal = self.invoice_line_id.price_subtotal

    # @api.constrains('new_price_unit', 'new_price_subtotal', 'adjustment_type', 'invoice_line_id')
    # def _check_valid_prices(self):
    #     for rec in self:
    #         if rec.adjustment_type == 'unit_price' and rec.new_price_unit <= 0:
    #             raise ValidationError(_("New unit price must be greater than zero for unit price adjustment."))
    #         if rec.adjustment_type == 'subtotal' and rec.new_price_subtotal <= 0:
    #             raise ValidationError(_("New subtotal must be greater than zero for subtotal adjustment."))


# --- Main Model Modification ---
class SBCreditNoteApply(models.Model, models.AbstractModel):
    _name = 'sb.credit.note.apply'
    _description = 'Credit Note Application and Invoice Unit Price Adjustment'
    _inherit = ['mail.thread', 'mail.activity.mixin']

    name = fields.Char(string='Reference', required=True, default='New')
    state = fields.Selection([
        ('draft', 'Draft'),
        ('to_approve', 'To Approve'),
        ('approved', 'Approved'),
        ('done', 'Done'),
        ('cancel', 'Cancelled'),
    ], string='Status', default='draft', readonly=True, copy=False, tracking=True)

    application_type = fields.Selection([
        ('credit_note', 'Create Credit Note'),
        ('invoice_price_adjustment', 'Invoice Unit Price Adjustment'),
    ], string='Application Type', required=True, default='invoice_price_adjustment')

    sale_order_id = fields.Many2one('sale.order', string='Source Sales Order',
                                    domain=[('invoice_status', 'in', ['invoiced', 'to invoice'])],
                                    help='Select the source sales order for this credit note application.')
    customer_id = fields.Many2one('res.partner', string='Customer', related='sale_order_id.partner_id', store=True)
    currency_id = fields.Many2one('res.currency', related='sale_order_id.currency_id')
    sale_order_line_ids = fields.Many2many('sale.order.line',
                                           string='Sales Order Lines',
                                           compute='_compute_sale_order_line_ids',
                                           store=True,
                                           help='Sales order lines associated with the selected sales order.')

    invoice_ids = fields.Many2many('account.move',
                                   string='Associated Invoices',
                                   compute='_compute_invoice_ids',
                                   store=True,
                                   help='Invoices associated with the selected sales order.')
    select_invoice = fields.Many2one('account.move', string='Select Invoice')
    available_so_to_adjust_ids = fields.Many2many('sale.order',
                                                  string='Available SOs for Adjustment (Filtered)',
                                                  compute='_compute_available_so_to_adjust',
                                                  help='Sales orders filtered based on invoice status that can have their unit price adjusted.')

    selected_sale_order_to_adjust_id = fields.Many2one('sale.order',
                                                       string='Select MO',
                                                       domain="[('id', 'in', available_so_to_adjust_ids)]",
                                                       help='Select the sales order whose invoice unit prices need to be adjusted.')

    related_invoices_for_adjustment = fields.Many2many(
        'account.move',
        string='Related Invoices for Adjustment',
        compute='_compute_related_invoices_for_adjustment',
        store=True,
        relation='sb_cn_apply_move_rel',
        column1='adjustment_id',
        column2='invoice_id',
        help='All related draft/unpaid invoices for the selected SO for price adjustment.'
    )
    adjustment_line_ids = fields.One2many('sb.credit.note.apply.line', 'adjustment_id',
                                          string='Invoice Lines to Adjust',
                                          copy=True)

    reason = fields.Text(string='Reason', help='Reason for the credit note or price adjustment.')
    sb_workflow_template_id = fields.Many2one('sb.workflow.template', string="Approval Workflow Template",
                                              domain="[('sb_workflow_template_item_ids', '!=', False),"
                                                     "('workflow_type', 'in', ['other', 'account'])]")
    sb_workflow_id = fields.Many2one('sb.workflow', string="Current Approval Workflow", copy=False)
    sb_workflow_state = fields.Selection(related="sb_workflow_id.state", readonly=True, string="Approval Status",
                                         store=True)
    sb_workflow_item_ids = fields.One2many(related="sb_workflow_id.sb_workflow_item_ids", string="Approval Items")
    sb_workflow_attachments_ids = fields.One2many(related="sb_workflow_id.sb_workflow_attachments_ids",
                                                  string="Approval Attachments")

    sb_workflow_ids = fields.One2many('sb.workflow', 'sb_credit_note_apply_id', string="Approval Workflows")

    sb_workflow_ids_sum = fields.Integer(compute="_compute_sb_workflow_ids_sum", store=True)
    approval_date = fields.Datetime(string='Approval Date', readonly=True)
    s_rma_ids = fields.One2many(related='sale_order_id.s_rma_ids', string='RMA')
    credit_qty = fields.Integer(string='Credit Qty', store=True, help='Total quantity of credit notes applied.')
    credit_product = fields.Many2one('product.product', string='Credit Product', store=True, help='Product used for credit notes.')
    inv_ids = fields.Many2many('account.move', string='Invoices',
                               store=True,
                               relation='sb_cn_apply_move_obj_rel',
                               column1='adjustment_id',
                               column2='invoice_id',
                               help='Invoices related to the credit note application.')

    def action_view_invoices(self):
        self.ensure_one()
        invoices = self.inv_ids
        action = self.env.ref('account.action_move_out_invoice_type').read()[0]
        if len(invoices) == 1:
            action.update({
                'views': [(self.env.ref('account.view_move_form').id, 'form')],
                'res_id': invoices.id,
            })
        else:
            action.update({
                'domain': [('id', 'in', invoices.ids)],
            })
        return action

    @api.onchange('s_rma_ids', 'sale_order_id')
    def _compute_credit_detail(self):
        """Compute the total credit quantity from related RMAs."""
        for rec in self:
            rec.credit_qty = sum(rma.product_uom_qty for rma in rec.s_rma_ids)
            rec.credit_product = rec.s_rma_ids and rec.s_rma_ids[0].product_id or False

    @api.onchange('application_type')
    def _clean_line(self):
        """Clear adjustment lines when application type changes."""
        if self.application_type:
            self.adjustment_line_ids = [(5, 0, 0)]

    @api.depends('sb_workflow_ids')
    def _compute_sb_workflow_ids_sum(self):
        for item in self:
            if item.sb_workflow_ids:
                item.sb_workflow_ids_sum = len(item.sb_workflow_ids)
            else:
                item.sb_workflow_ids_sum = 0

    @api.onchange('application_type')
    def _onchange_application_type(self):
        if self.sb_workflow_state != 'approval':
            if self.application_type == 'invoice_price_adjustment':
                self.sb_workflow_template_id = self.with_context(lang='en_US').env['sb.workflow.template'].search([('name', '=', 'Adjust the invoice unit price')], limit=1).id
            elif self.application_type == 'credit_note':
                self.sb_workflow_template_id = self.with_context(lang='en_US').env['sb.workflow.template'].search([('name', '=', 'Credit Note Application')], limit=1).id

    def create_workflow(self):

        """Create a new workflow based on the selected template."""
        if not self.adjustment_line_ids:
            raise UserError(_("What are you doing? There should be at least one piece of data, right?🤨"))
        for line in self.adjustment_line_ids:
            if line.original_price_unit == line.new_price_unit and self.application_type == 'invoice_price_adjustment':
                raise ValidationError(_("Hey, wake up! The new unit price is the same as the original for line: %s") % line.invoice_line_id.name)

        if self.sb_workflow_template_id:
            if len(self.sb_workflow_ids) > 0:
                workflow_sum = len(self.sb_workflow_ids) + 1
            else:
                workflow_sum = 1

            # 左右欄字段列表
            left_fields = ['application_type']
            if self.application_type == 'invoice_price_adjustment':
                left_fields.append('selected_sale_order_to_adjust_id')
            else:
                left_fields.append('sale_order_id')
            right_fields = ['reason']

            # 準備明細表格數據
            list_headers = ['INV Line', 'MPN', 'QTY', 'Original Unit Price', 'New Unit Price', 'Original Subtotal', 'tax', 'New Subtotal']
            list_rows = []
            for mo in self.adjustment_line_ids:
                list_rows.append([
                    mo.invoice_line_id.name or '',
                    mo.product_id.name if mo.product_id else '',
                    mo.quantity or '',
                    f"{mo.original_price_unit:.4f}" if mo.original_price_unit else '',
                    f"{mo.new_price_unit:.4f}" if mo.new_price_unit else '',
                    mo.original_price_subtotal or '',
                    mo.tax_ids.mapped('name') if mo.tax_ids else '',
                    f"{mo.new_price_subtotal:.4f}" if mo.new_price_subtotal else ''
                ])
            tp = self.sb_workflow_template_id
            workflow_data = {
                'l': [tp.get_value_and_label(f, self) for f in left_fields],
                'r': [tp.get_value_and_label(f, self) for f in right_fields],
                'list': {
                    'headers': list_headers,
                    'rows': list_rows,
                },
            }
            # document_ids = self.env['ir.attachment'].search([('res_model', '=', self._name), ('res_id', '=', self.id)])
            # 创建审批流
            new_wf_id = self.env['sb.workflow'].sudo().create({
                'name': self.name + _('-Approval Flow') + '-' + str(workflow_sum),
                'approval_time': fields.Datetime.now(),
                'workflow_type': 'sale',
                'res_id': self.id,
                'res_model': self._name,
                'workflow_data': workflow_data,

            })
            self.state = 'to_approve'
            if self.name == 'New':
                P = 'CDI' if self.application_type == 'credit_note' else 'AJI'
                MO = self.sale_order_id.name if self.application_type == 'credit_note' else self.selected_sale_order_to_adjust_id.name
                self.name = P + '-' + str(MO) + '-' + str(workflow_sum)
            self.sb_workflow_id = new_wf_id.id
            # 根据模板生成审批项
            item_audit_levels = []
            for item in self.sb_workflow_template_id.sb_workflow_template_item_ids:
                new_item = self.env['sb.workflow.item'].sudo().create({
                    'name': item.name,
                    'user_group_ids': item.user_group_ids.ids,
                    'audit_level': item.audit_level,
                    'sb_workflow_id': new_wf_id.id,
                    'state': 'wait_forward',
                    'is_plan': item.is_plan
                })
                # 收集生成的审核项的所有层级
                item_audit_levels.append(new_item.audit_level)
            # 去重排序
            item_audit_levels = list(set(item_audit_levels))
            item_audit_levels.sort()
            for n_item in new_wf_id.sb_workflow_item_ids:
                if n_item.audit_level == item_audit_levels[0]:
                    n_item.state = 'approval'
        else:
            raise ValidationError(_('Please select an approval template.💦'))

    @api.constrains('sb_workflow_state')
    def _check_sb_workflow_state(self):
        for record in self:
            if record.sb_workflow_state == 'approve' and record.state in ('draft', 'to_approve'):  # 审批通过自动确认订单
                record.state = 'done'
                if record.application_type == 'invoice_price_adjustment':
                    for line in record.adjustment_line_ids:
                        line.invoice_line_id.sudo().write({
                            'price_unit': line.new_price_unit,
                        })
                        for order_line in line.invoice_line_id.sale_line_ids:
                            order_line.order_id.sudo().action_unlock()
                            order_line.sudo().write({
                                'price_unit': line.new_price_unit,
                            })
                            order_line.order_id.sudo().action_lock()
                    record.inv_ids = [(6, 0, record.adjustment_line_ids.mapped('invoice_line_id.move_id').ids)]
                else:
                    name = self.env['ir.sequence'].next_by_code('account.move.cn')
                    account_move = self.env['account.move'].sudo().create({
                        'ref': _('Credit Note Application from %s') % record.name,
                        'move_type': 'out_refund',
                        'partner_id': record.sale_order_id.partner_id.id,
                        'invoice_origin': record.sale_order_id.name,
                        'invoice_date': fields.Date.today(),
                        'currency_id': record.sale_order_id.currency_id.id,
                        # 'internal_group': 'income',
                        'name': name
                    })
                    for line in record.adjustment_line_ids:
                        invoice_line = self.env['account.move.line'].sudo().create({
                            'move_id': account_move.id,
                            'name': line.label,
                            'product_id': line.invoice_line_id.product_id.id if line.invoice_line_id else False,
                            'quantity': line.quantity,
                            'tax_ids': [(6, 0, line.tax_ids.ids)] if line.tax_ids else [],
                            'price_unit': line.new_price_unit,
                            'sale_line_ids': [(6, 0, line.invoice_line_id.sale_line_ids.ids)] if line.invoice_line_id else False,
                        })
                    account_move.action_post()  # Post the credit note
                    record.inv_ids = [(6, 0, account_move.ids)]
            if record.sb_workflow_state == 'fail_to_approve' and record.state == 'to_approve':
                record.state = 'cancel'

    def unlink(self):
        for rec in self:
            if rec.state not in ('draft', 'cancel'):
                raise UserError(_("Only records in draft or cancelled state can be deleted."))
        return super(SBCreditNoteApply, self).unlink()

    @api.depends('sale_order_id')
    def _compute_sale_order_line_ids(self):
        for rec in self:
            rec.sale_order_line_ids = rec.sale_order_id.order_line if rec.sale_order_id else False

    @api.depends('sale_order_id')
    def _compute_invoice_ids(self):
        """
        计算与当前记录关联的销售订单的所有客户发票（去重、排序）。
        """
        for rec in self:
            if not rec.sale_order_id:
                rec.invoice_ids = False
                rec.select_invoice = False
                continue

            invoice_ids = (
                rec.sale_order_id.order_line.invoice_lines.move_id
                .filtered(lambda inv: inv.move_type == 'out_invoice')
            )
            rec.invoice_ids = [(6, 0, invoice_ids.ids)]
            rec.select_invoice = invoice_ids[0].id if invoice_ids else False

    @api.depends('application_type')
    def _compute_available_so_to_adjust(self):
        for rec in self:
            rec.available_so_to_adjust_ids = False  # Reset before recomputing
            if rec.application_type == 'invoice_price_adjustment':
                draft_unpaid_invoices = self.env['account.move'].search([
                    ('state', 'in', ('draft', 'open')),
                    ('move_type', '=', 'out_invoice'),
                    ('payment_state', 'in', ('not_paid', 'in_payment')),
                ])

                invoice_lines = draft_unpaid_invoices.mapped('invoice_line_ids')
                sale_order_lines = invoice_lines.mapped('sale_line_ids')
                related_sos = sale_order_lines.mapped('order_id')

                available_so_ids = list(set(related_sos.ids))

                rec.available_so_to_adjust_ids = [(6, 0, available_so_ids)]

    @api.depends('selected_sale_order_to_adjust_id')
    def _compute_related_invoices_for_adjustment(self):
        for rec in self:
            rec.related_invoices_for_adjustment = False  # Reset before recomputing
            if rec.selected_sale_order_to_adjust_id:
                selected_so_lines = rec.selected_sale_order_to_adjust_id.order_line
                invoice_lines_from_so = self.env['account.move.line'].search([
                    ('sale_line_ids', 'in', selected_so_lines.ids),
                ])
                related_invoices = invoice_lines_from_so.mapped('move_id')
                filtered_invoices = related_invoices.filtered(lambda inv:
                                                              inv.state != 'draft' and
                                                              inv.move_type == 'out_invoice' and
                                                              inv.payment_state in ('not_paid', 'in_payment'))
                rec.related_invoices_for_adjustment = [(6, 0, filtered_invoices.ids)]

    @api.onchange('selected_sale_order_to_adjust_id', 'select_invoice')
    def _onchange_selected_sale_order_to_adjust_id(self):
        if self.sale_order_id or self.selected_sale_order_to_adjust_id:
            self.adjustment_line_ids = [(5, 0, 0)]  # Clear existing lines
            lines_to_create = []
            if self.application_type == 'invoice_price_adjustment':
                if self.selected_sale_order_to_adjust_id:
                    for invoice in self.related_invoices_for_adjustment:
                        for inv_line in invoice.invoice_line_ids:
                            if inv_line.sale_line_ids and any(sol.order_id == self.selected_sale_order_to_adjust_id for sol in inv_line.sale_line_ids):
                                lines_to_create.append((0, 0, {
                                    'invoice_line_id': inv_line.id,
                                    'label': inv_line.name or inv_line.product_id.name,
                                    'new_price_unit': inv_line.price_unit,  # Default to original
                                    'new_price_subtotal': inv_line.price_subtotal,  # Default to original
                                    'adjustment_type': 'unit_price',  # Default adjustment type
                                    'quantity': inv_line.quantity,  # Ensure quantity is set
                                    'original_quantity': inv_line.quantity,  # Store original quantity for reference
                                }))
            if self.application_type == 'credit_note':
                if self.sale_order_id:
                    for invoice in self.select_invoice:
                        for inv_line in invoice.invoice_line_ids:
                            if inv_line.sale_line_ids and any(sol.order_id == self.sale_order_id for sol in inv_line.sale_line_ids):
                                lines_to_create.append((0, 0, {
                                    'invoice_line_id': inv_line.id,
                                    'label': inv_line.name or inv_line.product_id.name,
                                    'new_price_unit': inv_line.price_unit,
                                    'new_price_subtotal': inv_line.price_subtotal,
                                    'adjustment_type': 'unit_price',
                                    'quantity': self.credit_qty if self.s_rma_ids and self.credit_qty > 0 else inv_line.quantity,
                                    'original_quantity': inv_line.quantity,
                                }))
            if not lines_to_create:
                raise UserError(_("No invoice lines found for the selected sales order"))
            self.adjustment_line_ids = lines_to_create

    # Action Methods (Workflows)
    def action_submit_for_approval(self):
        for rec in self:
            if rec.state == 'draft':
                rec.state = 'to_approve'
                rec.message_post(body=_("Application submitted for approval."))

    def action_approve(self):
        for rec in self:
            if rec.state == 'to_approve':
                rec.state = 'approved'
                rec.approver_id = self.env.user.id
                rec.approval_date = fields.Datetime.now()
                if rec.application_type == 'credit_note':
                    rec._create_credit_note()
                elif rec.application_type == 'invoice_price_adjustment':
                    rec._apply_price_adjustment()  # Call the updated adjustment method
                rec.state = 'done'
                rec.message_post(body=_("Application approved and processed."))

    def action_cancel(self):
        for rec in self:
            if rec.state in ('draft', 'to_approve'):
                rec.state = 'cancel'
                rec.message_post(body=_("Application cancelled."))
                for item in rec.sb_workflow_ids:
                    item.sudo().write({'state': 'return'})

    def action_draft(self):
        for rec in self:
            if rec.state == 'cancel':
                rec.state = 'draft'
                rec.message_post(body=_("Application moved back to draft."))

    def _create_credit_note(self):
        if not self.invoice_ids:
            raise UserError(_("No associated invoices found to create a credit note."))

        for invoice in self.invoice_ids:
            credit_note_vals = {
                'ref': _('Credit Note Application from %s') % self.name,
                'move_type': 'out_refund',
                'partner_id': invoice.partner_id.id,
                'invoice_origin': invoice.name,
                'invoice_date': fields.Date.today(),
            }
            self.message_post(body=f"Processed credit note application for invoice: {invoice.name}")
        return True

    def _apply_price_adjustment(self):
        if not self.adjustment_line_ids:
            raise UserError(_("No invoice lines selected for adjustment."))

        for adj_line in self.adjustment_line_ids:
            inv_line = adj_line.invoice_line_id
            if inv_line.move_id.state != 'draft':
                raise UserError(_("Invoice '%s' for line '%s' is not in draft state and cannot be modified.") % (inv_line.move_id.name, inv_line.name))

            if adj_line.adjustment_type == 'unit_price':
                if adj_line.new_price_unit <= 0:
                    raise UserError(_("New unit price for line '%s' must be greater than zero.") % inv_line.name)
                inv_line.price_unit = adj_line.new_price_unit
                self.message_post(body=f"Adjusted unit price for invoice line '{inv_line.name}' to {adj_line.new_price_unit}")
            elif adj_line.adjustment_type == 'subtotal':
                if adj_line.new_price_subtotal <= 0:
                    raise UserError(_("New subtotal for line '%s' must be greater than zero.") % inv_line.name)
                # Calculate new unit price based on new subtotal and quantity
                if inv_line.quantity:
                    inv_line.price_unit = adj_line.new_price_subtotal / inv_line.quantity
                else:

                    inv_line.price_unit = adj_line.new_price_subtotal  # This might be less intuitive
                self.message_post(body=f"Adjusted subtotal for invoice line '{inv_line.name}' to {adj_line.new_price_subtotal}")

            # Recalculate invoice totals after each line update
            inv_line.move_id._onchange_invoice_line_ids()
        return True

    def action_print(self):
        self.ensure_one()
        if self.application_type == 'credit_note':
            if self.currency_id.name == 'THB':
                report_external_id = 'sb_sale.s_credit_note_action'
            else:
                report_external_id = 'sb_sale.report_sb_sinv'
        elif self.application_type == 'invoice_price_adjustment':
            report_external_id = 'sb_sale.sb_inv_picking_list_report_action'
        return self.env.ref(report_external_id).with_context(lang='en_US').report_action(self.inv_ids)
